﻿#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <QMessageBox>
#include <QDateTime>

#define TIMEMS          qPrintable(QTime::currentTime().toString("HH:mm:ss zzz"))
#define TIME            qPrintable(QTime::currentTime().toString("HH:mm:ss"))
#define QDATE           qPrintable(QDate::currentDate().toString("yyyy-MM-dd"))
#define QTIME           qPrintable(QTime::currentTime().toString("HH-mm-ss"))
#define DATETIME        qPrintable(QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss"))
#define STRDATETIME     qPrintable(QDateTime::currentDateTime().toString("yyyy-MM-dd-HH-mm-ss"))
#define STRDATETIMEMS   qPrintable(QDateTime::currentDateTime().toString("yyyy-MM-dd-HH-mm-ss-zzz"))
#define MyDATETIMEMS    qPrintable(QDateTime::currentDateTime().toString("MM-dd HH:mm:ss zzz"))

//CRC高位字节值表
const quint8 auchCRCHi[] = {
    0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x01, 0xc0,
    0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41,
    0x00, 0xc1, 0x81, 0x40, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0,
    0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
    0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
    0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
    0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
    0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
    0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40
};

//CRC低位字节值表
const quint8 auchCRCLo[] = {
    0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06,
    0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD,
    0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
    0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A,
    0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4,
    0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
    0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3,
    0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,
    0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
    0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29,
    0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED,
    0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
    0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60,
    0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67,
    0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
    0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,
    0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E,
    0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
    0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71,
    0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92,
    0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
    0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B,
    0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B,
    0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
    0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42,
    0x43, 0x83, 0x41, 0x81, 0x80, 0x40
};


/*************************************************************************
    Function:       	GetDataCheck
    Description:	输入1组数据，然后计算该组数据的CRC16校验码
    Calls:          	// 被本函数调用的函数清单
    Called By:      	// 调用本函数的函数清单

    Input:
            *pData: 要进行校验的数据指针
            len: 要进行校验的数据长度
    Output:

    Return:
            CRC16校验码
*************************************************************************/
static quint16 GetCrc16(quint8 *pData, quint16 len)
{
    quint8 chHiCrc = 0xff;			//高CRC字节初始化
    quint8 chLowCrc = 0xff;		//低CRC字节初始化
    quint8 uIndex = 0;				//CRC循环中的索引
    quint8 HL;

    while(len--){//传输消息缓冲区
        uIndex = chHiCrc ^ *pData++;	//计算CRC
        HL = auchCRCHi[uIndex];
        chHiCrc = chLowCrc ^ HL;
        HL = auchCRCLo[uIndex];
        chLowCrc = HL;
    }

    return ((chHiCrc << 8) | chLowCrc);
}

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow),
    serialPortInfo(nullptr)
{
    ui->setupUi(this);
    this->setFixedSize(this->width(), this->height());
    ui->overTimeEdit->setText(QString::number(overTick));
    ui->overTimeEdit->setValidator(new QIntValidator(500, 10000, this));

    serialPort = new QSerialPort(this);
    serialPort->setBaudRate(QSerialPort::Baud9600);
    serialPort->setDataBits(QSerialPort::Data8);
    serialPort->setParity(QSerialPort::OddParity);
    serialPort->setStopBits(QSerialPort::OneStop);
    serialPort->setFlowControl(QSerialPort::NoFlowControl);

    timerWnd = new QTimer(this);

    connect(serialPort, &QSerialPort::errorOccurred, this, &MainWindow::SlotPortError);
    connect(timerWnd, SIGNAL(timeout()), this, SLOT(SlotTimer()));
    connect(serialPort, &QSerialPort::readyRead, this, &MainWindow::SlotRead);

    refreshSerialPort();
}

MainWindow::~MainWindow()
{
    disconnect(timerWnd, SIGNAL(timeout()), this, SLOT(SlotTimer()));
    disconnect(serialPort, &QSerialPort::readyRead, this, &MainWindow::SlotRead);

    delete ui;
    closePort();
}

void MainWindow::on_serialBtn_clicked()
{
    if(isOpen) {
        timerWnd->stop();
        closePort();

        ui->serialBtn->setText(u8"打开串口");
        ui->serialPortBox->setEnabled(true);
        ui->refreshSerialBtn->setEnabled(true);
        ui->overTimeEdit->setEnabled(true);
    }
    else {
        closePort();

        QString port = ui->serialPortBox->currentText();
        serialPortInfo = new QSerialPortInfo(port);
        serialPort->setPort(*serialPortInfo);

        isOpen = serialPort->open(QIODevice::ReadWrite);
        if(!isOpen) {
            closePort();
        }
        else {
            ui->serialBtn->setText(u8"关闭串口");
            ui->serialPortBox->setEnabled(false);
            ui->refreshSerialBtn->setEnabled(false);
            ui->overTimeEdit->setEnabled(false);

            uint tick = ui->overTimeEdit->text().toUInt();
            if(tick < 500) {
                tick = 500;
                ui->overTimeEdit->setText("500");
            }
            timerWnd->setInterval(tick);
            timerWnd->start();
        }
    }
}

void MainWindow::on_clearBtn_clicked()
{
    std::lock_guard<std::mutex> lock(mutexCalcNum);

    sendNum = 0;
    recvNum = 0;
    continuousNum = 0;
    conNum = 0;

    ui->frameRatioEdit->setText("0");
    ui->frameRatioEdit_2->setText("0");
    ui->sendNumEdit->setText("0");
    ui->recvNumEdit->setText("0");
    ui->logTextEdit->clear();
}

void MainWindow::SlotTimer()
{
    static unsigned char buf[8] = {0x01, 0x02, 0x00, 0xA8, 0x00, 0x04, 0xF8, 0x29};

    if(isOpen) {
        if(sendNum > 0) {
            double ratio = sendNum;

            ratio -= recvNum;
            ratio /= sendNum;
            ratio *= 100.00;
            ui->frameRatioEdit->setText(QString::number(ratio, 10, 2));
        }

        writeLog("SND", (char *)buf, 8);

        if(recvNum < sendNum) {
            quint32 div = sendNum - recvNum;
            if(div != conNum) {
                conNum = div;
                continuousNum++;
                ui->frameRatioEdit_2->setText(QString::number(continuousNum));
                QString tmp = QString("continuous:%1 ERR").arg(div);
                writeWndLog(tmp);
            }
        }

        serialPort->write((char *)buf, 8);
        sendNum++;
        ui->sendNumEdit->setText(QString::number(sendNum));
    }
}

void MainWindow::SlotRead()
{
    QByteArray readByte = serialPort->readAll();

    if(readByte.isEmpty()) return;

    char *data = readByte.data();
    int dlen = readByte.length();

    writeLog("RCV", data, dlen);
    if(GetCrc16((quint8 *)data, dlen) == 0) {
        recvNum++;
        ui->recvNumEdit->setText(QString::number(recvNum));
    }
}

void MainWindow::SlotPortError(QSerialPort::SerialPortError error)
{
    if(error != QSerialPort::NoError) {
        QMessageBox::information(0, "tips", u8"串口提示错误");
        if(isOpen) {
            on_serialBtn_clicked();
        }
    }
}

void MainWindow::closePort()
{
    if(serialPort->isOpen()) {
        serialPort->close();
    }
    isOpen = false;

    if(serialPortInfo) {
        delete serialPortInfo;
        serialPortInfo = nullptr;
    }
}

void MainWindow::refreshSerialPort()
{
    ui->serialPortBox->clear();
    for(auto &dev:QSerialPortInfo::availablePorts())
    {
        QSerialPort com;

        com.setPort(dev);
        if(com.open(QIODevice::ReadWrite)) {
            ui->serialPortBox->addItem(com.portName());
            com.close();
        }
    }
}

void MainWindow::on_refreshSerialBtn_clicked()
{
    refreshSerialPort();
}

QString MainWindow::byteArrayToHexStr(const QByteArray &data)
{
    QString temp = "";
    QString hex = data.toHex();

    for (int i = 0; i < hex.length(); i = i + 2) {
        temp += hex.mid(i, 2) + " ";
    }

    return temp.trimmed().toUpper();
}

void MainWindow::writeLog(const char *title, char *buf, int len)
{
    QByteArray tmp = QByteArray((char *)buf, len);
    QString tmp1 = byteArrayToHexStr(tmp);
    QString tmp2 = QString("%1:%2").arg(title).arg(tmp1);

    writeWndLog(tmp2);
}

void MainWindow::writeWndLog(const QString& data)
{
    QString msg = QString("[%1]%2").arg(MyDATETIMEMS).arg(data);
    ui->logTextEdit->appendPlainText(msg);

    if(ui->logTextEdit->blockCount() > 40000) {
        on_fileSaveBtn_clicked();
    }
}

void MainWindow::on_fileSaveBtn_clicked()
{
    QString filename = QString("%1/%2SerialLog.txt").arg(qApp->applicationDirPath()).arg(STRDATETIME);
    QString data = ui->logTextEdit->toPlainText();

    QFile f(filename);
    if(f.open(QFile::WriteOnly | QFile::Text)) {
        f.write(data.toUtf8());
        f.close();
    }

    ui->logTextEdit->clear();
}
